#pragma once
#ifndef _KH_ENGINE_NET_H
#define _KH_ENGINE_NET_H
#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Units.h>
#include <Kharlia/Core/ObjectBase.h>

namespace kharlia { namespace engine {

class FReplicaManager;
class CPlayer;
class CWorld;

enum EPacketID {
    ID_DEBUG_MESSAGE = ID_USER_PACKET_ENUM,
    ID_CHANNEL_OPEN_REQUEST,
    ID_CHANNEL_OPEN_REPLY,
    ID_CHANNEL_CLOSE,
    ID_CHANNEL_MESSAGE,
    ID_ACTOR_RPC,
    ID_CLIENT_SETUP
};

const uint16 DEBUG_MESSAGE_MAX_SIZE = 0x100;

namespace ENetRole { enum Type {
    NONE,
    SIM_PROXY,
    AUTO_PROXY,
    AUTHORITY
};}

enum EFieldType {
    // Basic number types, replicate by value
    FT_INT = 0,
    FT_UINT,
    FT_FLOAT,
    FT_DOUBLE,
    FT_ENUM,
    FT_BOOL,
    FT_RAKGUID,
    // Field types, replicate by value
    FT_VECTOR3F = 30,
    FT_VECTOR3I,
    FT_VECTOR2F,
    FT_VECTOR2I,
    FT_RECTI,
    FT_RECTF,
    FT_SIZEI,
    FT_SIZEF,
    // Boost.Python type wrappers
    FT_PYOBJECT = 100,
    FT_PYSTR,
    FT_PYUNICODE,
    // Everything else
    FT_ACTOR = 150, // TPtr<CActor>
    FT_COMPONENT, // TPtr<AComponent>
    FT_WEAK_ACTOR, // TWeakPtr<CActor>
    FT_WEAK_COMPONENT, // TWeakPtr<AComponent>
    FT_RESOURCE, // shared_ptr<FResource>
    // Special
    FT_SENTRY = 250,
};

const uint8 FF_REPNOTIFY = 0x01;

struct FFieldInfo {
    // Macro-specified variables
    EFieldType Type; // Variable type
    uint8 Flags;
    char const* Name; // Variable name
    uint16 Offset; // Offset of value in its object
    uint16 Size; // Size in bytes for storage
    uint16 ClassSize; // Stores class size in bytes
    uint16 ArraySize; // > 0 if array
    uint16 Index; // Enumerated index
};

KH_ENGINE_API FFieldInfo const* GetSentryField();

inline uint16 GetNextFreeIndex(FFieldInfo const* fields) {
    uint16 index = 0;
    for (int i = 0; fields[i].Type != FT_SENTRY; i++) {
        index = fields[i].Index + 1;
    }
    return index;
}

#define DECL_CLASSINFO() \
    static FFieldInfo const* GetClassFieldInfo();\
    virtual void GetFullFieldInfo(std::vector<FFieldInfo const*>& output);

#define BEGIN_CLASSINFO(CLASS, BASECLASS) \
    void CLASS::GetFullFieldInfo(std::vector<FFieldInfo const*>& output) {\
        BASECLASS::GetFullFieldInfo(output);\
        FFieldInfo const* fieldInfo = CLASS::GetClassFieldInfo();\
        for (int i = 0; fieldInfo[i].Type != FT_SENTRY; i++) {\
            output.push_back(fieldInfo + i);\
        }\
    }\
    FFieldInfo const* CLASS::GetClassFieldInfo() {\
        typedef CLASS classtypedef;\
        static uint16 index = GetNextFreeIndex(BASECLASS::GetClassFieldInfo()); \
        static FFieldInfo nvi[] = {

#define BEGIN_CLASSINFO_NB(CLASS) \
    void CLASS::GetFullFieldInfo(std::vector<FFieldInfo const*>& output) {\
        FFieldInfo const* fieldInfo = CLASS::GetClassFieldInfo();\
        for (int i = 0; fieldInfo[i].Type != FT_SENTRY; i++) {\
            output.push_back(fieldInfo + i);\
        }\
    }\
    FFieldInfo const* CLASS::GetClassFieldInfo() {\
        typedef CLASS classtypedef;\
        static uint16 index = 0; \
        static FFieldInfo nvi[] = {

#define END_CLASSINFO() \
            {FT_SENTRY, 0, nullptr, 0, 0, 0, 0, 0xFFFF}\
        };\
        return nvi;\
    }

#define DEF_FIELD(name, type, flags) DEF_FIELD_ARRAY(name, type, flags, 0)

#define DEF_FIELD_ARRAY(name, type, flags, size) \
    {type, flags, #name, offsetof(classtypedef, name), \
     TDatamapFieldSizeDeducer<type>::Size, sizeof(classtypedef), size, index++},

// Field Sizes

template<EFieldType FieldType>
struct TDatamapFieldSizeDeducer {
    static const int Size = 0;
    static int FieldSize() { return 0; }
};

#define _DS(FIELDTYPE, FIELDSIZE) \
    template<> struct TDatamapFieldSizeDeducer<FIELDTYPE> { \
        static const int Size = FIELDSIZE;\
        static int GetFieldSize() { return FIELDSIZE; }};

_DS(FT_INT, sizeof(int))
_DS(FT_UINT, sizeof(uint))
_DS(FT_FLOAT, sizeof(float))
_DS(FT_DOUBLE, sizeof(double))
_DS(FT_ENUM, sizeof(int))
_DS(FT_BOOL, sizeof(bool))
_DS(FT_PYOBJECT, sizeof(object))
_DS(FT_PYSTR, sizeof(object))
_DS(FT_PYUNICODE, sizeof(object))
_DS(FT_VECTOR3F, sizeof(FVector3F))
_DS(FT_VECTOR3I, sizeof(FVector3I))
_DS(FT_VECTOR2F, sizeof(FVector2F))
_DS(FT_VECTOR2I, sizeof(FVector2I))
_DS(FT_RECTI, sizeof(FRectI))
_DS(FT_RECTF, sizeof(FRectF))
_DS(FT_SIZEI, sizeof(FSizeI))
_DS(FT_SIZEF, sizeof(FSizeF))
_DS(FT_ACTOR, sizeof(TPtr<core::CObject>)) // reference
_DS(FT_COMPONENT, sizeof(TPtr<core::CObject>)) // reference
_DS(FT_WEAK_ACTOR, sizeof(TPtr<core::CObject>))
_DS(FT_WEAK_COMPONENT, sizeof(TPtr<core::CObject>))
_DS(FT_RESOURCE, sizeof(shared_ptr<void>))

static_assert(sizeof(TPtr<core::CObject>) == 8, "");

#undef _DS

// Functions

inline rn::MessageID GetPacketIdentifier(rn::Packet *p) {
    if (p == nullptr)
        return 0xFF;
    if (p->data[0] == ID_TIMESTAMP) {
        RakAssert(p->length > sizeof(rn::MessageID) + sizeof(rn::Time));
        return p->data[sizeof(rn::MessageID) + sizeof(rn::Time)];
    }
    else
        return p->data[0];
}

inline bool IgnorePacketIdentifier(rn::BitStream* bs) {
    if (!bs)
        return false;
    rn::MessageID id;
    bs->Read(id);
    if (id == ID_TIMESTAMP) {
        bs->IgnoreBytes(sizeof(rn::Time) + sizeof(rn::MessageID));
    }
    return true;
}

template <EFieldType FieldType>
int GetFieldSize() { return TDatamapFieldSizeDeducer<FieldType>::Size; }
int GetFieldSize(EFieldType type);

KH_ENGINE_API FFieldInfo const* GetFieldInfo(FFieldInfo const** fields, char const* name);

class FReplicaManager;
class FNetPeer;

class KH_ENGINE_API FRMConnection: public rn::Connection_RM3 {
    FReplicaManager* mReplicaManager;
    TWeakPtr<CPlayer> mPlayer;

public:
    FRMConnection(
        rn::SystemAddress addr,
        rn::RakNetGUID guid
    ):
        Connection_RM3(addr, guid),
        mPlayer() {}

    virtual rn::Replica3* AllocReplica(rn::BitStream*, rn::ReplicaManager3*);

    TPtr<CPlayer> GetPlayer() const { return mPlayer.Lock(); }
    void SetPlayer(TPtr<CPlayer> const& p) { mPlayer = p; }
    FReplicaManager const* GetReplicaManager() const { return mReplicaManager; }

    friend class FReplicaManager;
};

class KH_ENGINE_API FReplicaManager: public rn::ReplicaManager3 {
    FNetPeer* mPeer;
    CWorld* mWorld;

public:
    FReplicaManager(FNetPeer* peer, CWorld* world): mPeer(peer), mWorld(world) {}
    
    void ReferenceExisting();
    FNetPeer* GetPeer() const { return mPeer; }
    CWorld* GetWorld() const { return mWorld; }
    virtual rn::Connection_RM3* AllocConnection(rn::SystemAddress const&, rn::RakNetGUID) const;
    virtual void DeallocConnection(rn::Connection_RM3*) const;
    rn::Replica3* GetReplicaByNetworkID(rn::NetworkID networkId);
    //TPtr<CActor> GetActorByNetworkID(rn::NetworkID networkId) {
    //    auto r = GetReplicaByNetworkID(networkId);
    //    return r ? static_cast<ANetwork*>(r)->GetActor() : TPtr<CActor>();
    //}

    friend class FRMConnection;
};


class FNetHandler {
public:
    virtual bool OnReceive(rn::Packet& packet, rn::MessageID id) { return true; }
    virtual void OnClientConnected(rn::RakNetGUID guid, rn::SystemAddress addr) {}
    virtual void OnClientDisconnected(rn::RakNetGUID guid, rn::SystemAddress addr, bool lost) {}
    virtual void OnServerConnected(rn::RakNetGUID guid, rn::SystemAddress addr) {}
    virtual void OnServerDisconnected(rn::RakNetGUID guid, rn::SystemAddress addr, bool lost) {}
};

class KH_ENGINE_API FNetPeer: public boost::noncopyable {
    rn::RakPeerInterface* mBase;
    bool mIsEnabled;
    bool mIsServer;
    bool mIsClient;
    rn::SystemAddress mConnectingTo;
    std::vector<rn::RakNetGUID> mClientConnections;
    rn::RakNetGUID mServerConnection;
    FReplicaManager* mReplicaManager;
    rn::NetworkIDManager mIDManager;
    FNetHandler* mHandler;

public:

    FNetPeer();
    ~FNetPeer();

    void Tick(float deltaTime);
    void Listen();
    void Connect(str host, uint16 port);
    void Shutdown();
    bool CloseConnection(rn::AddressOrGUID target, bool notify);
    void InitReplicaManager(CWorld* world);
    void ShutdownReplicaManager();

    rn::RakPeerInterface* GetBase() const { return mBase; }
    bool GetIsEnabled() const { return mIsEnabled; }
    bool GetIsServer() const { return mIsServer; }
    bool GetIsClient() const { return mIsClient; }
    rn::SystemAddress GetConnectingTo() const { return mConnectingTo; }
    std::vector<rn::RakNetGUID> const& GetClientConnections() const { return mClientConnections; }
    rn::RakNetGUID GetServerConnection() const { return mServerConnection; }
    FReplicaManager* GetReplicaManager() const { return mReplicaManager; }
    rn::NetworkIDManager const* GetIDManager() const { return &mIDManager; }
    FNetHandler* GetHandler() const { return mHandler; }
    void SetHandler(FNetHandler* value) { mHandler = value; }

};

}}

#endif